/*
 * Catalogador.cpp
 *
 *  Created on: 30/05/2011
 *      Author: Jonathan
 */

#include "Catalogador.h"
#include "estructuras/lista/IteradorLista.h"
#include "estructuras/lista/ComparadorContador.h"

#include <cmath>
Catalogador::Catalogador() {
	this->raiz = NULL;
	this->autores = AutoresManager::getInstance();
}

/**
 * A partir de una lista de storePalabras, genera una lista de strings con todas las palabras sin duplicados
 * @param lista lista con los stores
 * @return la lista sin duplicados
 */
Lista<Contador> Catalogador::ListaPalabrasSinDuplicados(
		Lista<StorePalabras*> &lista, Comparador<Contador> * ordenamiento) {
	IteradorLista<StorePalabras*> iterador = lista.iterador();
	IteradorLista<string> iteradorPalabras;
	StorePalabras conjunto("");
	Lista<string> listaAux;
	string palabraAux;
	while (iterador.tieneSiguiente()) {
		listaAux.copiarLista(iterador.siguiente()->inorden());
		iteradorPalabras = listaAux.iterador();
		while (iteradorPalabras.tieneSiguiente()) {
			palabraAux = iteradorPalabras.siguiente();
			conjunto.insert(palabraAux);
		}
	}

	return conjunto.inorden(ordenamiento);
}
/**
 * Filtra una lista por existencia y no existencia de una palabra en ellas
 * @param palabra divisora
 * @param lineas todas las lineas
 * @param listaNo Lista donde se devuelven las lineas en las q no existe la palabra
 * @param listaSi Lista donde se devuelven las lineas en las q existe la palabra
 */
void Catalogador::filtrarListaPorExistencia(string palabra,
		Lista<StorePalabras*> &lineas, Lista<StorePalabras*> &listaNo,
		Lista<StorePalabras*> &listaSi) {
	listaNo.clear();
	listaSi.clear();
	IteradorLista<StorePalabras*> iteradorLineas = lineas.iterador();
	StorePalabras * pstoreAux;
	while (iteradorLineas.tieneSiguiente()) {
		pstoreAux = iteradorLineas.siguiente();
		if (pstoreAux->getOcurrencias(palabra) == 0) {
			listaNo.add(pstoreAux);
		} else {
			listaSi.add(pstoreAux);
		}
	}
}

/**
 * Filtra una lista de lineas por un autor
 * @param autor el autor pedido
 * @param lineas la lista de lineas a filtrar
 * @param filtrado el resultado
 */

float Catalogador::contarLineasPorAutor(string autor,
		Lista<StorePalabras*> &lineas) {
	IteradorLista<StorePalabras*> iteradorLineas = lineas.iterador();
	StorePalabras * pstoreAux;
	float contador = 0;
	while (iteradorLineas.tieneSiguiente()) {
		pstoreAux = iteradorLineas.siguiente();
		if (pstoreAux->getAutor() == autor) {
			contador++;
		}
	}
	return contador;
}
/**
 * Devuelve la entropia del un conjunto
 * @param lineas1 la cantidad de lineas correspondiente a una clase del conjunto
 * @param lineas2 la cantidad de lineas correspondiente a la otra clase del conjunto
 * @return la entrop�a del conjunto
 */
float Catalogador::calcularEntropia(float lineas1, float lineas2) {
	float resultado = 0;

	if (lineas1 != 0) {
		resultado = (lineas1 / (lineas1 + lineas2)) * log2(
				lineas1 / (lineas1 + lineas2));
	}
	if ( lineas2 != 0){
		resultado = resultado + (lineas2 / (lineas1 + lineas2)) * log2(
				lineas2 / (lineas1 + lineas2));
	}
	resultado = resultado * (-1);
	return resultado;
}

/**
 * Entrena el arbol de decisi�n
 * @param archivoEntrenamiento la lista de ejemplos
 */
void Catalogador::entrenar(Lista<StorePalabras*> & archivoEntrenamiento) {
	this->raiz = entrenarRec(archivoEntrenamiento);

}

/**
 * Borra recursivamente el arbol
 * @param nodoActual el nodo en el cual esta parada la funci�n
 */
void Catalogador::borrarRec(NodoCatalogo * nodoActual) {
	NodoCatalogo * izq, *der;
	if (nodoActual) {
		izq = nodoActual->izq;
		der = nodoActual->der;
		delete nodoActual;
		borrarRec(izq);
		borrarRec(der);
	}

}

/**
 * Destructor
 */
Catalogador::~Catalogador() {
	borrarRec(raiz);
}

/**
 * Si con el conjunto de entrenamiento actual se puede determinar ya un autor devuelve quien es, sino devuelve ""
 * @param archivoEntrenamiento el conjunto de ejemplos actual
 * @return el autor si es que se puede determinar
 */
string Catalogador::autorCorrespondiente(
		Lista<StorePalabras*> & archivoEntrenamiento) {
	string autor = "", autorAux = "";
	bool mismoAutor = true;
	IteradorLista<StorePalabras*> iterador = archivoEntrenamiento.iterador();
	autorAux = iterador.siguiente()->getAutor();
	while (iterador.tieneSiguiente() && mismoAutor) {
		mismoAutor = mismoAutor && (autorAux
				== iterador.siguiente()->getAutor());
	}
	if (mismoAutor) {
		return autorAux;
	}
	return autor;
}

string Catalogador::determinarAutorRec(NodoCatalogo *& nodo,
		StorePalabras & linea) {
	if (nodo) {
		if (nodo->izq == NULL && nodo->der == NULL) {
			return nodo->palabra;
		}
		if (linea.getOcurrencias(nodo->palabra) != 0) {
			return determinarAutorRec(nodo->der, linea);
		} else
			return determinarAutorRec(nodo->izq, linea);
	}
	return "";
}
/**
 * Milagrosamente arma el �rbol recursivamente
 * @param nodo nodo que se esta determinando actualmente
 * @param archivoEntrenamiento los ejemplos que se van a analizar para determinar el dato del nodo
 */
void Catalogador::entrenarRec(NodoCatalogo *& nodo,
		Lista<StorePalabras*> & archivoEntrenamiento) {
	//variables
	if (archivoEntrenamiento.size() == 0)
		return;

	int x, contador = 0;
	Lista<Contador> palabrasSinDuplicados;
	Lista<StorePalabras*> lineasSi, lineasNo;
	Contador palabraAux;
	string autor1, autorDeterminado;
	autor1 = autores->getAutor1();
	PalabraGanancia gananciaAux, mejorGanancia;
	float lineasAutor1, lineasAutor1Si, lineasAutor1No;
	float entropiaTotal(0.0);
	IteradorLista<Contador> iteradorPalabras;
	ComparadorContadorOcurrenciasDesc * comparador =
			new ComparadorContadorOcurrenciasDesc();
	// m�todo

	autorDeterminado = this->autorCorrespondiente(archivoEntrenamiento);

	if (autorDeterminado == "") {
		palabrasSinDuplicados.copiarLista(
				this->ListaPalabrasSinDuplicados(archivoEntrenamiento,
						comparador));

		lineasAutor1 = contarLineasPorAutor(autor1, archivoEntrenamiento);
		entropiaTotal = calcularEntropia(lineasAutor1,
				archivoEntrenamiento.size() - lineasAutor1);

		iteradorPalabras = palabrasSinDuplicados.iterador();
		x = palabrasSinDuplicados.size() / 2;
		mejorGanancia.setPalabra("");
		mejorGanancia.setGanancia(-2);

		while (iteradorPalabras.tieneSiguiente() && contador < x) {
			palabraAux = iteradorPalabras.siguiente();
			filtrarListaPorExistencia(palabraAux.getPalabra(),
					archivoEntrenamiento, lineasNo, lineasSi);
			gananciaAux.setPalabra(palabraAux.getPalabra());
			lineasAutor1Si = contarLineasPorAutor(autor1, lineasSi);
			lineasAutor1No = contarLineasPorAutor(autor1, lineasNo);

			gananciaAux.setGanancia(
					entropiaTotal - ((lineasSi.size()
							/ archivoEntrenamiento.size()) * calcularEntropia(
							lineasAutor1Si, lineasSi.size()) - lineasAutor1Si)
							- ((lineasNo.size() / archivoEntrenamiento.size())
									* calcularEntropia(lineasAutor1No,
											lineasNo.size() - lineasAutor1No)));
			//TODO:PROTEGERSE POR DIVISION POR 0
			gananciaAux.setGanancia(
					gananciaAux.getGanancia() / calcularEntropia(
							lineasSi.size(), lineasNo.size()));
			if (gananciaAux.getGanancia() > mejorGanancia.getGanancia()) {
				mejorGanancia = gananciaAux;
			}
			contador++;
		}

		// ahora deber�a tener la palabra con mayor ganancia de informaci�n en el primer elemento de la lista de ganancias
		nodo = new NodoCatalogo(mejorGanancia.getPalabra());
		filtrarListaPorExistencia(nodo->palabra, archivoEntrenamiento,
				lineasNo, lineasSi);
		//y ahora contin�o generando el arbol para las lineas que s� contienen esta palabra y las que no.
		entrenarRec(nodo->der, lineasSi);
		entrenarRec(nodo->izq, lineasNo);
	} else {
		nodo = new NodoCatalogo(autorDeterminado, true);
	}

	delete comparador;
}

/**
 * Milagrosamente arma el �rbol recursivamente
 */
NodoCatalogo * Catalogador::entrenarRec(
		Lista<StorePalabras*> & archivoEntrenamiento) {
	string autorDeterminado, mejorPalabra;

	// m�todo
	Lista<StorePalabras*> lineasSi, lineasNo;
	NodoCatalogo * raiz = new NodoCatalogo();
	autorDeterminado = this->autorCorrespondiente(archivoEntrenamiento);
	if (autorDeterminado != "") {
		raiz->palabra = autorDeterminado;
	} else {
		mejorPalabra = determinarMejorPalabra(archivoEntrenamiento);
		raiz = new NodoCatalogo(mejorPalabra);
		filtrarListaPorExistencia(raiz->palabra, archivoEntrenamiento,
				lineasNo, lineasSi);
		raiz->der = entrenarRec(lineasSi);
		raiz->izq = entrenarRec(lineasNo);
	}
	//esto queda
	return raiz;
}

string Catalogador::determinarAutor(StorePalabras & parrafo) {
	return this->determinarAutorRec(raiz, parrafo);
}

string Catalogador::determinarMejorPalabra(
		Lista<StorePalabras*> & archivoEntrenamiento) {
	//int x, contador = 0; // variables no usadas en la ultima revision
	Lista<Contador> palabrasSinDuplicados;
	Lista<StorePalabras*> lineasSi, lineasNo;
	Contador palabraAux;
	string autor1, autorDeterminado;
	autor1 = autores->getAutor1();
	PalabraGanancia gananciaAux, mejorGanancia;
	float lineasAutor1, lineasAutor1Si, lineasAutor1No;
	float entropiaTotal(0.0);
	IteradorLista<Contador> iteradorPalabras;
	ComparadorContadorOcurrenciasDesc * comparador =
			new ComparadorContadorOcurrenciasDesc();

	palabrasSinDuplicados.copiarLista(
			this->ListaPalabrasSinDuplicados(archivoEntrenamiento, comparador));

	lineasAutor1 = contarLineasPorAutor(autor1, archivoEntrenamiento);
	entropiaTotal = calcularEntropia(lineasAutor1,
			archivoEntrenamiento.size() - lineasAutor1);

	iteradorPalabras = palabrasSinDuplicados.iterador();
	//	x = palabrasSinDuplicados.size() / 2;


	mejorGanancia.setPalabra("");
	mejorGanancia.setGanancia(-2);

	//while (iteradorPalabras.tieneSiguiente() && contador < x) {
	while (iteradorPalabras.tieneSiguiente()) {
		palabraAux = iteradorPalabras.siguiente();
		filtrarListaPorExistencia(palabraAux.getPalabra(),
				archivoEntrenamiento, lineasNo, lineasSi);
		gananciaAux.setPalabra(palabraAux.getPalabra());
		lineasAutor1Si = contarLineasPorAutor(autor1, lineasSi);
		lineasAutor1No = contarLineasPorAutor(autor1, lineasNo);
		float ent1 =((float) lineasSi.size()	/ (float) archivoEntrenamiento.size())
				* calcularEntropia(lineasAutor1Si, lineasSi.size()- lineasAutor1Si);
		float ent2= ((float) lineasNo.size()/ (float) archivoEntrenamiento.size())
				* calcularEntropia(lineasAutor1No,
						lineasNo.size() - lineasAutor1No);
		gananciaAux.setGanancia(entropiaTotal - ent1 - ent2);

		//TODO:PROTEGERSE POR DIVISION POR 0
		//		gananciaAux.setGanancia(
		//				gananciaAux.getGanancia() / calcularEntropia(lineasSi.size(),
		//						lineasNo.size()));
		if (gananciaAux.getGanancia() > mejorGanancia.getGanancia()) {
			mejorGanancia = gananciaAux;
		}
		//contador++;
	}

	return mejorGanancia.getPalabra();
}

